/**********************************************************************
  HttpRequestManager - Submit http 'get' and 'post' requests with a
                       QNetworkAccessManager and receive the results

  Copyright (C) 2017-2018 by Patrick Avery

  This source code is released under the New BSD License, (the "License").

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 ***********************************************************************/

#include "httprequestmanager.h"

#include <QDebug>
#include <QNetworkRequest>

HttpRequestManager::HttpRequestManager(
  const std::shared_ptr<QNetworkAccessManager>& networkManager, QObject* parent)
  : QObject(parent), m_networkManager(networkManager), m_requestCounter(0)
{
  // This is done so that handleGet and handlePost are always ran in the
  // main thread
  connect(this, &HttpRequestManager::signalGet, this,
          &HttpRequestManager::handleGet);
  connect(this, &HttpRequestManager::signalPost, this,
          &HttpRequestManager::handlePost);
}

size_t HttpRequestManager::sendGet(QUrl url)
{
  std::unique_lock<std::mutex> lock(m_mutex);

  QNetworkRequest request(url);

  emit signalGet(request, m_requestCounter);

  return m_requestCounter++;
}

size_t HttpRequestManager::sendPost(QUrl url, const QByteArray& data)
{
  std::unique_lock<std::mutex> lock(m_mutex);

  QNetworkRequest request(url);
  request.setHeader(QNetworkRequest::ContentTypeHeader,
                    "application/x-www-form-urlencoded");

  emit signalPost(request, data, m_requestCounter);

  return m_requestCounter++;
}

bool HttpRequestManager::containsData(size_t i) const
{
  std::unique_lock<std::mutex> lock(m_mutex);
  return m_receivedReplies.find(i) != m_receivedReplies.end();
}

const QByteArray& HttpRequestManager::data(size_t i) const
{
  std::unique_lock<std::mutex> lock(m_mutex);

  static const QByteArray empty = "";

  if (m_receivedReplies.find(i) == m_receivedReplies.end())
    return empty;

  return m_receivedReplies.at(i);
}

void HttpRequestManager::handleGet(QNetworkRequest request, size_t requestId)
{
  std::unique_lock<std::mutex> lock(m_mutex);

  QNetworkReply* reply = m_networkManager->get(request);

  connect(reply, (void (QNetworkReply::*)(QNetworkReply::NetworkError))(
                   &QNetworkReply::error),
          this, &HttpRequestManager::handleError);
  connect(reply, &QNetworkReply::finished, this,
          &HttpRequestManager::handleFinished);

  m_pendingReplies[requestId] = reply;
}

void HttpRequestManager::handlePost(QNetworkRequest request, QByteArray data,
                                    size_t requestId)
{
  std::unique_lock<std::mutex> lock(m_mutex);

  QNetworkReply* reply = m_networkManager->post(request, data);

  connect(reply, (void (QNetworkReply::*)(QNetworkReply::NetworkError))(
                   &QNetworkReply::error),
          this, &HttpRequestManager::handleError);
  connect(reply, &QNetworkReply::finished, this,
          &HttpRequestManager::handleFinished);

  m_pendingReplies[requestId] = reply;
}

void HttpRequestManager::handleError(QNetworkReply::NetworkError ec)
{
  std::unique_lock<std::mutex> lock(m_mutex);

  // Make sure the sender is a QNetworkReply
  QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
  if (!reply) {
    qDebug() << "Error in" << __FUNCTION__ << ": sender() is not a"
             << "QNetworkReply!";
    return;
  }

  // Make sure this HttpRequestManager owns this reply
  auto it =
    std::find_if(m_pendingReplies.begin(), m_pendingReplies.end(),
                 [reply](const std::pair<size_t, QNetworkReply*>& item) {
                   return reply == item.second;
                 });

  // If not, print an error and return
  if (it == m_pendingReplies.end()) {
    qDebug() << "Error in" << __FUNCTION__ << ": sender() is not owned by"
             << "this HttpRequestManager instance!";
    return;
  }

  size_t receivedInd = it->first;

  // Print a message for some of the more common errors
  if (ec == QNetworkReply::ConnectionRefusedError)
    qDebug() << "QNetworkReply received an error: connection refused";
  else if (ec == QNetworkReply::RemoteHostClosedError)
    qDebug() << "QNetworkReply received an error: remote host closed";
  else if (ec == QNetworkReply::HostNotFoundError)
    qDebug() << "QNetworkReply received an error: host not found";
  else if (ec == QNetworkReply::TimeoutError)
    qDebug() << "QNetworkReply received an error: timeout";
  else
    qDebug() << "QNetworkReply received error code:" << ec;

  m_receivedReplies[receivedInd] = reply->readAll();

  m_pendingReplies.erase(receivedInd);

  reply->deleteLater();

  // Emit a signal
  emit received(receivedInd);
}

void HttpRequestManager::handleFinished()
{
  std::unique_lock<std::mutex> lock(m_mutex);

  // Make sure the sender is a QNetworkReply
  QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender());
  if (!reply) {
    qDebug() << "Error in" << __FUNCTION__ << ": sender() is not a"
             << "QNetworkReply!";
    return;
  }

  // Make sure this HttpRequestManager owns this reply
  auto it =
    std::find_if(m_pendingReplies.begin(), m_pendingReplies.end(),
                 [reply](const std::pair<size_t, QNetworkReply*>& item) {
                   return reply == item.second;
                 });

  // If not, just return
  if (it == m_pendingReplies.end())
    return;

  size_t receivedInd = it->first;

  m_receivedReplies[receivedInd] = reply->readAll();

  m_pendingReplies.erase(receivedInd);

  reply->deleteLater();

  // Emit a signal
  emit received(receivedInd);
}
